`include "CP0Defines.svh"
`include "defines.svh"

module CP0 (
    input logic clk,
    input logic rst,

    //写入CP0信号
    input logic CP0Wr,                           //CP0写使能  
    input logic[4:0] CP0WrAddr,                  //写入CPO的地址，不包含sel部分，rd
    input logic[31:0] CP0WrData,                 //写入CP0的数值     

    //从CP0中读
    input logic[4:0] CP0RdAddr,                 //从CP0中要读数的地址
   /* output logic[31:0] CP0RdData,                //从CP0中读的数值*/

    input logic [31:0] WrongVirturalAddr,       //导致发生地址例外的虚地址，写入CP0BadVAddr
    input logic [31:0] CurrentInstrAddr,        //当前指令的PC
    input ExceptionType ExceptionTypeEnd,       //最终的异常类型
    input AsynExceptionType HardwareInterrupt,  //6个外部硬件中断输入
    input logic IsDelayDlot,                          //发生异常的指令是否是分支延迟槽
    input logic Is_Interrupt,

    //CP0寄存器
    output logic [31:0] CP0RdData,
    output logic [31:0] CP0BadVAddr,
    output logic [31:0] CP0Status,
    output logic [31:0] CP0Cause,
    output logic [31:0] CP0EPC,
    output logic [31:0] CP0Count,
    output logic [31:0] CP0Compare, 

    output logic Cp0TimerInterrupt            //是否有定时中断发生
);
    logic tick;                 //用于起到CP0count寄存器每两个周期加1的效果

    //assign Cp0TimerInterrupt=CP0Cause[30];      //核内定时中断的中断状态位

    //****************************************************************
    //写CP0BadVAddr中的数据
    //BadVAddr寄存器只读
    //****************************************************************   
    always_ff @( posedge clk,negedge rst ) begin
        if(!rst)begin
            CP0BadVAddr<=32'b0;
        end
        else if(ExceptionTypeEnd.WrongAddrInDataRd||ExceptionTypeEnd.WrongAddrInDataWr)begin
            CP0BadVAddr<=WrongVirturalAddr;
        end
        else if(ExceptionTypeEnd.WrongAddrInIF)
            CP0BadVAddr<=CurrentInstrAddr;
        else
            CP0BadVAddr<=CP0BadVAddr;
    end

    //****************************************************************
    //写CP0Status中的数据
    //Status寄存器可读可写，IM7...IM0,EXL,IE是可以写的，其他为恒定值
    //****************************************************************  
    always_ff @( posedge clk,negedge rst ) begin
        if(!rst)begin
            CP0Status<=32'b0000_0000_0100_0000_0000_0000_0000_0000;
        end
        else if(CP0Wr==1'b1&&CP0WrAddr==`CP0StatusAddr)begin      //MTC0写入
            CP0Status[15:8]<=CP0WrData[15:8];
            CP0Status[1]<=CP0WrData[1];
            CP0Status[0]<=CP0WrData[0];
        end
        else if(ExceptionTypeEnd[8:1]!=8'b0000_0000)begin   //任何指令报例外时
            CP0Status[1]<=1'b1;
        end
        else if(ExceptionTypeEnd.Eret==1'b1)begin           //遇到ERET指令时
            CP0Status[1]<=1'b0;
        end
    end
    //****************************************************************
    //           写CP0Cause寄存器中的内容
    //           除了IP1~IP0域之外，其他域均只可读
    //****************************************************************
    //Cause中的BD域

    always_ff @(posedge clk,negedge rst)begin
        if(!rst)begin
            CP0Cause[29:16]<=14'b0;
            CP0Cause[7]<=1'b0;
            CP0Cause[1:0]<=2'b0;
        end
    end

    always_ff @( posedge clk,negedge rst ) begin 
        if(!rst)begin
            CP0Cause[31]<=1'b0;
        end
        else if(!CP0Status[1]&&ExceptionTypeEnd[8:1]!=8'b0000_0000)begin        //指令报例外时更新
            CP0Cause[31]<=IsDelayDlot;
        end
    end
    //Cause中的TI域
    always_ff @( posedge clk,negedge rst ) begin 
        if(!rst)begin
            CP0Cause[30]<=1'b0;
            Cp0TimerInterrupt <= 1'b0;
        end
        else if(CP0Wr&&CP0WrAddr==`CP0CompareAddr)begin                     //CP0Compare写入时，清0
            CP0Cause[30]<=1'b0;
            Cp0TimerInterrupt <= 1'b0;
        end
        else if(CP0Count==CP0Compare&&CP0Compare!=32'b0)begin                //count和compare相等且compare不为0时置1
            CP0Cause[30]<=1'b1;
            Cp0TimerInterrupt <= 1'b1;
        end
    end
    //Cause中的IP7~IP2域
    always_ff @( posedge clk,negedge rst ) begin  
        if(!rst)begin
            CP0Cause[15]<='0;
            CP0Cause[14:10]<='0;
        end
        else begin
            CP0Cause[15]<=HardwareInterrupt[5]|CP0Cause[30];                //采样中断信号
            CP0Cause[14:10]<=HardwareInterrupt[4:0];
        end
    end
    //Cause中的IP1和IP0域,仅通过软件来进行更新
    always_ff @( posedge clk,negedge rst ) begin 
        if(!rst)begin
            CP0Cause[9:8]<=2'b0;
        end
        else if(CP0Wr&&CP0WrAddr==`CP0CauseAddr)begin
            CP0Cause[9:8]<=CP0WrData[9:8];
        end
    end
    //Cause中的EXcode域，不可写，仅在指令报例外的时候填入对应的例外编码
    always_ff @( posedge clk,negedge rst ) begin 
        if(!rst)begin
            CP0Cause[6:2]<=5'b0;
        end
        else if(ExceptionTypeEnd.WrongAddrInDataRd||ExceptionTypeEnd.WrongAddrInIF)begin
            CP0Cause[6:2]<=`ExcCodeAdEL;
        end
        else if(ExceptionTypeEnd.WrongAddrInDataWr)begin
            CP0Cause[6:2]<=`ExcCodeAdES;
        end
        else if(ExceptionTypeEnd.Overflow)begin
            CP0Cause[6:2]<=`ExcCodeOv;
        end
        else if(ExceptionTypeEnd.Syscall)begin
            CP0Cause[6:2]<=`ExcCodeSys;
        end
        else if(ExceptionTypeEnd.Break)begin
            CP0Cause[6:2]<=`ExcCodeBp;
        end
        else if(ExceptionTypeEnd.ReservedInstr)begin
            CP0Cause[6:2]<=`ExcCodeRI;
        end
        else if(ExceptionTypeEnd.Interrupt)begin
            CP0Cause[6:2]<=`ExcCodeInt;
        end
    end

    //****************************************************************
    //           写CP0EPC寄存器中的内容
    //           可读可写
    //****************************************************************
    always_ff @( posedge clk,negedge rst ) begin 
        if(!rst)begin
            CP0EPC<=32'b0;
        end
        else if(!CP0Status[1]&&ExceptionTypeEnd[8:1]!=8'b0000_0000)begin   //Status寄存器的EXL为1时，发生例外不更新
            if(IsDelayDlot)begin        //如果发生例外的延迟槽指令，则记录前一条J或B型指令
                CP0EPC<=CurrentInstrAddr-4;
            end
//         else if(Is_Interrupt)begin          //貌似软件中断是2个周期，没有明白为什么。所以软件中断出现的地址要是WB级地址+4
//             CP0EPC<=CurrentInstrAddr+4;
//         end
            else begin
                CP0EPC<=CurrentInstrAddr;
            end
        end
        else if(CP0Wr&&CP0WrAddr==`CP0EPCAddr)begin
            CP0EPC<=CP0WrData;
        end
    end

    //****************************************************************
    //           写CP0Count寄存器中的内容
    //           可读可写
    //****************************************************************
    always_ff @( posedge clk,negedge rst ) begin 
        if(!rst)begin
            CP0Count<=32'b0;
        end
        else if(CP0Wr&&CP0WrAddr==`CP0CountAddr)begin
            CP0Count<=CP0WrData;
        end
        else if(tick)begin
            CP0Count<=CP0Count+1;
        end
    end

    always_ff @( posedge clk,negedge rst ) begin 
        if(!rst)begin
            tick<=1'b1;
        end
        else begin
            tick<=~tick;
        end
    end
    //****************************************************************
    //           写CP0Count寄存器中的内容
    //           可读可写
    //****************************************************************
    always_ff @(posedge clk,negedge rst ) begin
        if(!rst)begin
            CP0Compare<=32'b0;
        end
        else if(CP0Wr&&CP0WrAddr==`CP0CompareAddr)begin
            CP0Compare<=CP0WrData;
        end
    end

    //从CP0中读数据
    always_comb begin 
        case (CP0RdAddr)
        `CP0BadVAddrAddr: begin
            CP0RdData=CP0BadVAddr;
        end
        `CP0StatusAddr:begin
            CP0RdData=CP0Status;
        end
        `CP0CauseAddr:begin
            CP0RdData=CP0Cause;
        end
        `CP0EPCAddr:begin
            CP0RdData=CP0EPC;
        end
        `CP0CountAddr:begin
            CP0RdData=CP0Count;
        end
        `CP0CompareAddr:begin
            CP0RdData=CP0Compare;
        end
            default:CP0RdData='x;
        endcase
    end
endmodule
